#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <filesystem>  // C++17
#include <fstream>
#include <memory>
#include <time.h>
#include <cstdio>
#include <sstream>
#include "Mutex.hpp"

using namespace std;

namespace LogModule
{
    using namespace LockModule;

    // 获取一下当前的时间
    string CurrentTime()
    {
        time_t time_stamp = time(nullptr);  // 获取一下当前的时间戳
        struct tm curr;  // 存时间的返回值
        localtime_r(&time_stamp, &curr);  // localtime 的可重入版本
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d", curr.tm_year+1900, curr.tm_mon+1, curr.tm_mday, curr.tm_hour, curr.tm_min, curr.tm_sec);
        return buffer;
    }

    const string defaultlogpath = "./log/";  // 默认路径
    const string defaultlogname = "log.txt";  // 默认文件名

    // 日志等级
    enum class LogLevel
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL  // 致命的错误
    };

    // 我们拿到level时是一个个的整数，但我们想打印的是字符串，所以要转换一下
    string LogLevel_To_String(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:    
            return "UNKNOWN";
        }
    }

    // 刷新策略
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const string& message) = 0;
    };

    // 控制台策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        void SyncLog(const string& message)
        {
            LockGuard lock_guard(_lock);
            cout << message << endl;
        }
    private:
        Mutex _lock;
    };

    // 文件级（磁盘）策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const string& logpath = defaultlogpath, const string& logname = defaultlogname)
            :_logpath(logpath)
            ,_logname(logname)
        {
            LockGuard lockguard(_lock);
            // 首先要确定路径_logpath是存在的
            // 这里可以用C++17的新特性 filesystem
            if(filesystem::exists(_logpath))
            {
                return;
            }
            try
            {
                filesystem::create_directories(_logpath);  // 没有就新建一个
            }
            catch(filesystem::filesystem_error &e)
            {
                cerr << e.what() << endl;  // 打印错误信息  
            }
        }

        // 刷新日志
        void SyncLog(const string& message)
        {
            LockGuard lockguard(_lock);
            string log = _logpath + _logname;  // ./log/log.txt
            ofstream out(log, ios::app);
            if(!out.is_open())
            {
                return;
            }
            out << message << endl;  // 将message写到out中  
            out.close();
        }
    private:
        string _logpath;
        string _logname;
        Mutex _lock;  // 锁，保护资源安全，要往文件里面写东西
    };

    // 日志类:构建日志字符串,根据策略，进行刷新
    class Logger
    {
    public:
        Logger()
        {
            // 默认采用ConsoleLogStrategy(显示器/控制台)策略
            _strategy = make_shared<ConsoleLogStrategy>();
        }

        void EnableConsoleLogStrategy()
        {
            _strategy = make_shared<ConsoleLogStrategy>();
        }

        void EnableFileLogStrategy()
        {
            _strategy = make_shared<FileLogStrategy>();
        }

        // 内部类，实现RAII⻛格的⽇志格式化和刷新
        // 一条完整的信息，就是一个LogMessage对象： [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分（<< "hello world" << a << b << 3.14）
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, const string& filename, uint32_t line, Logger& logger)
                :_currtime(CurrentTime())
                ,_level(level)
                ,_pid(::getpid())
                ,_filename(filename)
                ,_line(line)
                ,_logger(logger)
            {
                stringstream ssbuffer;
                ssbuffer << "[" << _currtime << "] " << "[" << LogLevel_To_String(_level) << "] " << "[" << _pid << "] " << "[" << _filename << "] " << "[" << _line << "] ";
                _loginfo = ssbuffer.str();  // 每次创建一个就会自动有前半部分
            }

            // 为了实现不同类型的输出info到日志里面，所以我们用一个模板参数，并且不知道输出多少个，需要重载我们的 <<
            template<typename T>
            LogMessage& operator <<(const T& info)
            {
                stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;  // 返回当前对象，保存数据
            }

            ~LogMessage()
            {
                if(_logger._strategy)
                {
                    _logger._strategy->SyncLog(_loginfo);  // 执行插入完，自动析构
                }
            }
        private:
            string _currtime;  // 当前日志的时间（不是时间戳）
            LogLevel _level;  // 日志等级
            pid_t _pid;  // 进程pid
            string _filename;  // 源文件名称
            uint32_t _line;  // 文件行号
            string _loginfo;  // 日志的信息
            Logger& _logger;  // 负责根据不同的策略进行刷新
        };  

        // 就是要拷贝，不引用
        LogMessage operator()(LogLevel level, const string& filename, uint32_t line)  // 以后我们希望通过Log(Debug)等的方式实现，重载operator()
        {
            return LogMessage(level, filename, line, *this);
        }
    private:
        shared_ptr<LogStrategy> _strategy;  // 日志刷新的策略方案
    };

    Logger logger;

    #define LOG(level) logger(level, __FILE__, __LINE__)  // 源文件名会被 __FILE__ 替换掉， 行号会被 __LINE__ 替换掉，然后我们也做了运算符重载，刚好也传参了，实际上是 LogMessage 打印
    #define ENABLE_CONSOLE_LOG() logger.EnableConsoleLogStrategy()
    #define ENABLE_FILE_LOG() logger.EnableFileLogStrategy()
} 